{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# akshare股票数据，csv指标文件"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "止损:金叉后跌破买入价的%."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "import backtrader as bt  \n",
    "import pandas as pd\n",
    "from datetime import datetime"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 导入股票\n",
    "data_stock"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "import akshare as ak\n",
    "# 获取上证指数的历史行情数据  \n",
    "data_stock = ak.stock_zh_index_daily(symbol=\"sh000001\")  \n",
    "# print(data_stock)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>date</th>\n",
       "      <th>open</th>\n",
       "      <th>high</th>\n",
       "      <th>low</th>\n",
       "      <th>close</th>\n",
       "      <th>volume</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1990-12-19</td>\n",
       "      <td>96.050</td>\n",
       "      <td>99.980</td>\n",
       "      <td>95.790</td>\n",
       "      <td>99.980</td>\n",
       "      <td>126000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1990-12-20</td>\n",
       "      <td>104.300</td>\n",
       "      <td>104.390</td>\n",
       "      <td>99.980</td>\n",
       "      <td>104.390</td>\n",
       "      <td>19700</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>1990-12-21</td>\n",
       "      <td>109.070</td>\n",
       "      <td>109.130</td>\n",
       "      <td>103.730</td>\n",
       "      <td>109.130</td>\n",
       "      <td>2800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1990-12-24</td>\n",
       "      <td>113.570</td>\n",
       "      <td>114.550</td>\n",
       "      <td>109.130</td>\n",
       "      <td>114.550</td>\n",
       "      <td>3200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>1990-12-25</td>\n",
       "      <td>120.090</td>\n",
       "      <td>120.250</td>\n",
       "      <td>114.550</td>\n",
       "      <td>120.250</td>\n",
       "      <td>1500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8172</th>\n",
       "      <td>2024-06-07</td>\n",
       "      <td>3053.915</td>\n",
       "      <td>3065.025</td>\n",
       "      <td>3031.044</td>\n",
       "      <td>3051.279</td>\n",
       "      <td>31634248400</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8173</th>\n",
       "      <td>2024-06-11</td>\n",
       "      <td>3042.134</td>\n",
       "      <td>3043.200</td>\n",
       "      <td>3013.861</td>\n",
       "      <td>3028.045</td>\n",
       "      <td>31048194200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8174</th>\n",
       "      <td>2024-06-12</td>\n",
       "      <td>3025.296</td>\n",
       "      <td>3042.042</td>\n",
       "      <td>3021.309</td>\n",
       "      <td>3037.468</td>\n",
       "      <td>27945210600</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8175</th>\n",
       "      <td>2024-06-13</td>\n",
       "      <td>3038.086</td>\n",
       "      <td>3040.402</td>\n",
       "      <td>3022.822</td>\n",
       "      <td>3028.919</td>\n",
       "      <td>29786246300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8176</th>\n",
       "      <td>2024-06-14</td>\n",
       "      <td>3020.959</td>\n",
       "      <td>3037.905</td>\n",
       "      <td>3011.577</td>\n",
       "      <td>3032.633</td>\n",
       "      <td>34874675300</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>8177 rows × 6 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "            date      open      high       low     close       volume\n",
       "0     1990-12-19    96.050    99.980    95.790    99.980       126000\n",
       "1     1990-12-20   104.300   104.390    99.980   104.390        19700\n",
       "2     1990-12-21   109.070   109.130   103.730   109.130         2800\n",
       "3     1990-12-24   113.570   114.550   109.130   114.550         3200\n",
       "4     1990-12-25   120.090   120.250   114.550   120.250         1500\n",
       "...          ...       ...       ...       ...       ...          ...\n",
       "8172  2024-06-07  3053.915  3065.025  3031.044  3051.279  31634248400\n",
       "8173  2024-06-11  3042.134  3043.200  3013.861  3028.045  31048194200\n",
       "8174  2024-06-12  3025.296  3042.042  3021.309  3037.468  27945210600\n",
       "8175  2024-06-13  3038.086  3040.402  3022.822  3028.919  29786246300\n",
       "8176  2024-06-14  3020.959  3037.905  3011.577  3032.633  34874675300\n",
       "\n",
       "[8177 rows x 6 columns]"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data_stock"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 指标数据文件\n",
    "data_signals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 读取csv\n",
    "data_signals = pd.read_csv('data/bwwmacd1.csv') # 股票指标数据(大智慧导出)\n",
    "# 改变列名称\n",
    "data_signals.columns = ['date', 'M5', 'M10', 'M20']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 日期列转换时间类型\n",
    "data_stock['date'] = pd.to_datetime(data_stock['date'])\n",
    "data_signals['date'] = pd.to_datetime(data_signals['date'])\n",
    "\n",
    "# 时间索引\n",
    "data_stock.index = pd.to_datetime(data_stock['date'])\n",
    "data_signals.index = pd.to_datetime(data_signals['date'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# print(data_stock)\n",
    "# print(data_signals)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 截取数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义开始和结束日期  \n",
    "start_date = '2000-01-01'  # 例如：2023年1月1日  \n",
    "end_date = '2024-06-07'   # 例如：2023年6月30日  \n",
    "  \n",
    "# 将字符串转换为datetime对象（如果它们不是的话）  \n",
    "start_date = datetime.strptime(start_date, '%Y-%m-%d')  \n",
    "end_date = datetime.strptime(end_date, '%Y-%m-%d') \n",
    "\n",
    "mask = (data_stock.date >= start_date) & (data_stock.date <= end_date)  \n",
    "data_stock = data_stock.loc[mask] \n",
    "data_signals=data_signals.loc[mask] "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# print(data_stock)\n",
    "# print(data_signals)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 自定义数据源类\n",
    "这两个类都继承自 bt.feeds.PandasData，意味着它们能够处理Pandas DataFrame格式的数据。\n",
    "\n",
    "在策略中，您可以通过 self.datas[0] 访问股票数据，通过 self.datas[1] 访问信号数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 自定义数据源，这里仅展示框架，具体实现需要根据你的CSV文件格式进行调整  \n",
    "class MyStockData(bt.feeds.PandasData):  # MyStockData 类定义了如何从Pandas DataFrame中提取股票数据。\n",
    "    # params 元组指定了日期、开盘价、最高价、最低价、收盘价和交易量（如果有的话）在DataFrame中的列索引。\n",
    "    params = (  \n",
    "        ('datetime', 0),  # 日期在DataFrame中的列索引 \n",
    "        ('high', 1),      # 最高价在DataFrame中的列索引 \n",
    "        ('low', 2),       # 最低价在DataFrame中的列索引  \n",
    "        ('open', 3),      # 开盘价在DataFrame中的列索引  \n",
    "        ('close', 4),     # 收盘价在DataFrame中的列索引  \n",
    "        ('volume', 5),    # 交易量在DataFrame中的列索引（如果有的话）  \n",
    "    )  \n",
    "  \n",
    "class MySignalData(bt.feeds.PandasData):  # MySignalData 类用于处理信号数据。\n",
    "    # 指定了日期和信号在DataFrame中的列索引。\n",
    "    lines = ('M5','M10','M20', )\n",
    "    params = (  \n",
    "        ('datetime', 0),  # 日期在DataFrame中的列索引  \n",
    "        ('high', None),      # 最高价在DataFrame中的列索引 \n",
    "        ('low', None),       # 最低价在DataFrame中的列索引  \n",
    "        ('open', None),      # 开盘价在DataFrame中的列索引  \n",
    "        ('close', None),     # 收盘价在DataFrame中的列索引  \n",
    "        ('volume', None),    # 交易量在DataFrame中的列索引（如果有的话）  \n",
    "        ('M5', 1), # 假设'M5'是某种信号，这里重命名以避免混淆  \n",
    "        ('M10', 2), # 同上  \n",
    "        ('M20', 3), # 同上  \n",
    "    )  \n",
    "    \n",
    "        \n",
    "  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 买卖策略"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyStrategy(bt.Strategy):\n",
    "    params = (\n",
    "       ('stop_loss', 0.03),  # 止损比例，比如3%\n",
    "    )\n",
    "\n",
    "    def __init__(self):\n",
    "        self.order = None\n",
    "        # 记录买入价格\n",
    "        self.buy_price = None\n",
    "        \n",
    "        self.datasignalsM5 = self.datas[1].lines.M5  \n",
    "        self.datasignalsM10 = self.datas[1].lines.M10\n",
    "        self.datasignalsM20 = self.datas[1].lines.M20\n",
    "        \n",
    "        self.crossover = bt.ind.CrossOver(self.datasignalsM5, self.datasignalsM10)\n",
    "        self.up = self.datas[1].lines.M5[0] > self.datas[1].lines.M10[0]\n",
    "        self.down = self.datas[1].lines.M5[0] < self.datas[1].lines.M10[0]\n",
    "        pass\n",
    "\n",
    "    def downcast(self, amount, lot): #可买入资金\n",
    "\t    #使用整数除法 amount // lot 来找到 amount 可以被 lot 整除的最大次数。\n",
    "        return abs(amount // lot * lot) \n",
    "        \"\"\" \t   \n",
    "\t\tself 的出现意味着这个函数实际上是类的一个方法，而不是一个独立的函数。\n",
    "\t\tclass MyClass:  \n",
    " \t\t   def downcast(self, amount, lot):  \n",
    "  \t\t      return abs(amount // lot * lot)  \n",
    "\n",
    "\t\t# 使用方法  \n",
    "\t\tmy_instance = MyClass()  \n",
    "\t\tresult = my_instance.downcast(123, 10)  \n",
    "\t\tprint(result)  # 输出 120\n",
    "\t    \"\"\"\n",
    "    # 可以不要，但如果你数据未对齐，需要在这里检验\n",
    "    # def prenext(self):\n",
    "    #     print('prenext 执行 ', self.datetime.date(), self.getdatabyname('300015')._name\n",
    "    #           , self.getdatabyname('300015').close[0])\n",
    "    #     pass\n",
    "\n",
    "    def next(self):\n",
    "        # 计算止损价格\n",
    "        stop_price = self.buy_price * (1 - self.params.stop_loss) if self.buy_price else 0\n",
    "        \n",
    "        if self.order:\n",
    "            return\n",
    "        # 回测如果是最后一天，则不进行买卖\n",
    "        if pd.Timestamp(self.data.datetime.date(0)) == end_date:\n",
    "            return\n",
    "        if not self.position:  # 没有持仓\n",
    "            # 执行买入条件判断：收盘价格上涨突破20日均线；\n",
    "            # 不要在股票剔除日前一天进行买入\n",
    "            if self.crossover > 0:  # 如果信号为1，买入  \n",
    "                # 更新买入价格\n",
    "                self.buy_price = self.data.close[0]\n",
    "                print('执行买入') \n",
    "                order_value = self.broker.getvalue() * 0.98\n",
    "                order_amount = self.downcast(order_value / self.data.close[0], 100)\n",
    "                self.order = self.buy(self.datas[0], order_amount, name=self.datas[0]._name)\n",
    "\n",
    "        else:\n",
    "            # 执行卖出条件判断\n",
    "            # 如果当前价格低于止损价格，则卖出\n",
    "            # if self.data.close[0] < stop_price:\n",
    "            #     #print(f\"止损卖出 { self.data.datetime.date(0)}, {stop_price},{self.data0.close[0]}\")\n",
    "                \n",
    "            #     self.close()\n",
    "                    \n",
    "            if self.crossover < 0 :\n",
    "                # 执行卖出\n",
    "                print('执行卖出') \n",
    "                self.order = self.order_target_percent(self.datas[0], 0, name=self.datas[0]._name)\n",
    "                self.log(f'卖{self.datas[0]._name},price:{self.data.close[0]:.2f},pct: 0')\n",
    "            elif self.datas[1].lines.M5[0] > self.datas[1].lines.M10[0] and self.data.close[0] < stop_price: \n",
    "                print('止损')   \n",
    "                self.order = self.order_target_percent(self.datas[0], 0, name=self.datas[0]._name)\n",
    "                self.log(f'卖{self.datas[0]._name},price:{self.data.close[0]:.2f},pct: 0')\n",
    "        pass\n",
    "\n",
    "    def notify_order(self, order): # 用于通知和处理订单信息。\n",
    "        # 检查order.status是否属于两个可能的状态之一：order.Submitted 和 order.Accepted。\n",
    "        if order.status in [order.Submitted, order.Accepted]:\n",
    "            # Buy/Sell order submitted/accepted to/by broker - Nothing to do\n",
    "            return\n",
    "\n",
    "        # Check if an order has been completed\n",
    "        # Attention: broker could reject order if not enough cash\n",
    "        if order.status in [order.Completed, order.Canceled, order.Margin]:\n",
    "            if order.isbuy():\n",
    "                self.log(\n",
    "                    f\"买入{order.info['name']}, 成交量{order.executed.size}，成交价{order.executed.price:.2f} 订单状态：{order.status}\")\n",
    "                self.log('买入后当前资产：%.2f 元' % self.broker.getvalue())\n",
    "            elif order.issell():\n",
    "                self.log(\n",
    "                    f\"卖出{order.info['name']}, 成交量{order.executed.size}，成交价{order.executed.price:.2f} 订单状态：{order.status}\")\n",
    "                self.log('卖出后当前资产：%.2f 元' % self.broker.getvalue())\n",
    "            self.bar_executed = len(self)\n",
    "\n",
    "        # Write down: no pending order\n",
    "        self.order = None\n",
    "\n",
    "    def log(self, txt, dt=None): #日志-日期和信息\n",
    "        \"\"\"\n",
    "        输出日期\n",
    "        :param txt:\n",
    "        :param dt:\n",
    "        :return:\n",
    "        \"\"\"\n",
    "        dt = dt or self.datetime.date(0)  # 现在的日期\n",
    "        #'%s , %s' 是一个字符串模板，其中%s是一个占位符，用于表示一个字符串。\n",
    "        # 在这个模板中，有两个%s，这意味着我们需要为它们提供两个字符串值。\n",
    "        # (dt.isoformat(), txt) 是一个元组，它包含了两个值：dt.isoformat() 和 txt。\n",
    "        # isoformat方法默认会生成一个包含T的ISO 8601格式的字符串（如2023-10-23T14:30:00），\n",
    "        # 所以在输出的日期和时间之间会有一个T\n",
    "        print('%s , %s' % (dt.isoformat(), txt))\n",
    "\n",
    "    pass\n",
    "\n",
    "    def notify_trade(self, trade): #打印交易信息\n",
    "        '''可选，打印交易信息'''\n",
    "        pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建Cerebro引擎  \n",
    "cerebro = bt.Cerebro()  \n",
    "\n",
    "# 设置回测时间范围\n",
    "start_date = datetime(2000, 4, 3)\n",
    "end_date = datetime(2024, 5, 22)\n",
    "  \n",
    "# 添加数据源  \n",
    "# data0 = MyStockData(dataname=data_stock, fromdate=start_date, todate=end_date) \n",
    "data0 = bt.feeds.PandasData(dataname=data_stock, fromdate=start_date, todate=end_date)\n",
    "cerebro.adddata(data0)  \n",
    "\n",
    "data1 = MySignalData(dataname=data_signals, fromdate=start_date, todate=end_date)\n",
    "#data1 = bt.feeds.PandasData(dataname=data_signals, fromdate=start_date, todate=end_date) \n",
    "cerebro.adddata(data1)  \n",
    "  \n",
    "# 添加策略  \n",
    "cerebro.addstrategy(MyStrategy)  \n",
    "  \n",
    "# 设置初始资金和手续费\n",
    "start_cash = 1000000\n",
    "cerebro.broker.setcash(start_cash)\n",
    "cerebro.broker.setcommission(commission=0.002)\n",
    "\n",
    "# 添加策略分析指标\n",
    "cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='tradeanalyzer')  \n",
    "cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='annualReturn')  \n",
    "cerebro.addanalyzer(bt.analyzers.Returns, _name='annualizedReturns', tann=252)  # 使用 'annualizedReturns' 代替 '_Returns'  \n",
    "cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')  \n",
    "cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpeRatio')  # 使用 'sharpeRatio' 代替 'sharpe'  \n",
    "cerebro.addanalyzer(bt.analyzers.Returns, _name='totalReturns')  # 使用 'totalReturns' 代替重复的 'returns'  \n",
    "# 假设 'TimeReturn' 是有效的分析器  \n",
    "cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='timeReturn')  # 假设这是有效的\n",
    "\n",
    "#cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')\n",
    "\n",
    "\n",
    "# 运行回测\n",
    "results = cerebro.run()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#获取回测结果并打印\n",
    "port_value = cerebro.broker.getvalue()\n",
    "pnl = port_value - start_cash\n",
    "print(f\"初始资金: {start_cash}\\n回测期间：{start_date.strftime('%Y%m%d')}:{end_date.strftime('%Y%m%d')}\")\n",
    "print(f\"总资金: {round(port_value, 2)}\")\n",
    "print(f\"净收益: {round(pnl, 2)}\")#计算胜率\n",
    "total_trades = results[0].analyzers.tradeanalyzer.get_analysis()['total']['total']\n",
    "won_trades = results[0].analyzers.tradeanalyzer.get_analysis()['won']['total']\n",
    "win_rate = (won_trades / total_trades) * 100 if total_trades > 0 else 0\n",
    "print('总交易次数:', total_trades)\n",
    "print('盈利次数:', won_trades)\n",
    "print('胜率%:', win_rate)\n",
    "# 打印分析器输出结果\n",
    "# print(f\"初始资金: {start_cash}\\n回测期间:{start_date.strftime('%Y-%m-%d')} : {end_date.strftime('%Y-%m-%d')}\")\n",
    "# print('年度汇报:', results[0].analyzers.annualReturn.get_analysis())\n",
    "print('年化收益%:', results[0].analyzers.annualizedReturns.get_analysis()['rnorm100'])\n",
    "print('最大回撤比例%:', results[0].analyzers.drawdown.get_analysis().max.drawdown)\n",
    "print('夏普比率:', results[0].analyzers.sharpeRatio.get_analysis()['sharperatio'])\n",
    "print('累计收益：', results[0].analyzers.totalReturns.get_analysis()['rtot'])\n",
    "\n",
    "print(f'最后投资金额：{round(cerebro.broker.getvalue(), 2)}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 假设 annual_return_data 是您的 OrderedDict 数据  \n",
    "annual_return_data =  results[0].analyzers.annualReturn.get_analysis()\n",
    "  \n",
    "# 设定年份字段的最小宽度（不包括负号）和小数点后的位数  \n",
    "year_field_width = 4  # 假设年份最多4位  \n",
    "decimal_places = 4  # 小数点后的位数  \n",
    "  \n",
    "# 使用列表推导式来格式化字符串，确保小数点对齐  \n",
    "# 对于年份，我们不需要特殊处理，直接格式化即可  \n",
    "# 对于回报值，我们使用字符串格式化来确保它有一个固定的宽度，包括小数点和小数部分  \n",
    "lines = [f\"{year:>{year_field_width}d}, {return_value:+.{decimal_places}f}\" for year, return_value in annual_return_data.items()]  \n",
    "  \n",
    "# 使用 '\\n' 拼接这些行以换行  \n",
    "output = '\\n'.join(lines)  \n",
    "  \n",
    "# 打印结果  \n",
    "print('年度汇报:\\n' + output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from backtrader_plotting import Bokeh\n",
    "from backtrader_plotting.schemes import Tradimo\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plotconfig = {\n",
    "    'id:ind#0': dict(\n",
    "        subplot=True,\n",
    "    ),\n",
    "}\n",
    "b=Bokeh(style='bar',tabs='multi',scheme=Tradimo())# 传统白底，多页\n",
    "cerebro.plot(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import quantstats as qs\n",
    "    \n",
    "returns.index = returns.index.tz_convert(None)\n",
    "benchmark_ret = rawdata.close.pct_change().fillna(0)\n",
    "\n",
    "qs.reports.html(returns, benchmark=benchmark_ret, \n",
    "                output='stats.html', title='Stock Sentiment')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
